package fr.cephb.joperon.webapp;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;

import com.oreilly.servlet.MultipartRequest;
import com.sleepycat.db.DatabaseException;

import fr.cephb.io.IOUtils;
import fr.cephb.io.InputSource;
import fr.cephb.joperon.core.Ymd;
import fr.cephb.joperon.core.bio.Assembly;
import fr.cephb.joperon.core.db.PrjMgrDB;
import fr.cephb.joperon.core.entities.MPanel;
import fr.cephb.joperon.core.entities.PrjFin;
import fr.cephb.joperon.core.entities.PrjMgr;
import fr.cephb.joperon.core.entities.PrjSnp;
import fr.cephb.joperon.core.iterator.AbstractIterator;
import fr.cephb.joperon.core.iterator.DatabaseValueIterator;
import fr.cephb.joperon.tool.OperonFactory;
import fr.cephb.joperon.tool.snpinfo.SNPInfo;
import fr.cephb.joperon.webapp.jpa.Access;
import fr.cephb.joperon.webapp.jpa.User;
import fr.cephb.util.Cast;

public class AbstractOperonServlet extends HttpServlet
	{
	private static final long serialVersionUID = 1L;
	private File db_home;
	private String webTmpDir="/tmp";
	

	
	@Resource(name="jdbc/OperonDS")
	private DataSource datasource;
	
	@PersistenceContext(name="TopLinkPersistenceUnit2")
	private EntityManager topLink;
	
	// http://weblogs.java.net/blog/ss141213/archive/2005/12/entitymanagerpe_1.html
    @Resource private UserTransaction utx; 
    
    
    protected AbstractOperonServlet()
    	{
    	
    	}
    
    
	@Override
	public void init() throws ServletException
		{
		InputStream in= this.getClass().getResourceAsStream("LocalSettings.properties");
		if(in==null) throw new ServletException("Cannot read LocalSettings.properties");
			
		Properties prop= new Properties();
		try {
			prop.loadFromXML(in);
			in.close();
			} 
		catch (IOException e)
			{
			throw new ServletException(e);
			}
			
		String s= prop.getProperty("DB_HOME");
		if(s==null) throw new ServletException("ERROR:ServletContextListener: DB_HOME undefined");
		this.db_home= new File(s);
		
		s= prop.getProperty("WEB_TMP_DIR");
		if(s!=null && s.trim().length()>0)
			{
			this.webTmpDir=s;
			}
				
		}
	
	
	public DataSource getDatasource() {
		return datasource;
		}
	
	public EntityManager getTopLink() {
		return topLink;
		}
	
	public UserTransaction getUserTransaction()
		{
		return utx;
		}
	
	public File getDBHome()
		{
		return db_home;
		}
	
	public String getWebTmpDir() {
		return webTmpDir;
		}

	/** security : force an array of string to be non-null
	 * 
	 * @param array
	 * @return the original array if not null , or an empty array if the original was null
	 */
	protected static String[] arrayNotNull(String array[])
		{	
		if(array==null) array= new String[0];
		return array;
		}
	
	/** anwser if this string is only blank or null */
	protected static boolean isEmpty(String s)
		{
		return s==null || s.trim().length()==0;
		}
	
	/** log the stack trace */
	protected void debug()
		{
		try {
			throw new Exception();
			} 
		catch (Exception e) {
			StackTraceElement stack[]=e.getStackTrace();
			if(stack==null || stack.length<2) return;
			StackTraceElement trace=stack[1];
			getServletContext().log("[LOG] "+trace.getFileName()+":"+trace.getLineNumber());
			}
		}

	/** print the stack trace in the log */
	protected void debug(Object msg)
		{
		try {
			throw new Exception();
			} 
		catch (Exception e) {
			StackTraceElement stack[]=e.getStackTrace();
			if(stack==null || stack.length<2) return;
			StackTraceElement trace=stack[1];
			getServletContext().log("[LOG] "+trace.getFileName()+":"+trace.getLineNumber()+":"+msg);
			}
		}

	/** peform a simple validation */
	protected void verify(boolean test)throws ServletException
		{
		if(!test) throw new ServletException("Test failed");
		}
	
	/** compute the SHA1 key of the string */
	protected static String sha1(String string) throws ServletException
		{
	        try {
	        	byte[] hash = MessageDigest.getInstance("SHA-1").digest(string.getBytes("UTF-8"));
	        
	                StringBuilder hashString = new StringBuilder(42);
	
	                for (int i = 0; i < hash.length; ++i)
	                        {
	                    String x = Integer.toHexString(hash[i]&0xff);
	                    if (x.length()<2) hashString.append("0");
	                    hashString.append(x);
	                        }
	
	                return hashString.toString();
	        	}
	        catch(Exception err)
	        	{
	        	throw new ServletException(err);
	        	}
		}
	
	/** return an Access for this user and this project */
	protected Access getAccessByUserPid(User user,String pid) throws ServletException
		{
		if(user==null || pid==null) return null;
		List<?> rez= this.getTopLink().createNamedQuery("access.byUserIdAndProject").
			setParameter("userId", user.getId()).
			setParameter("project", pid).getResultList();
		if(rez.isEmpty())
			{
			return null;
			}
		return Access.class.cast(rez.get(0));
		}
	
	/** answer a user by his login and password */
	protected User getUserByLoginPassword(String login,String password) throws ServletException
		{
		if(login==null || password==null) return null;
		List<?> rez= this.getTopLink().createNamedQuery("user.byLoginPassword").
			setParameter("login", login).
			setParameter("sha1", sha1(password)).getResultList();
		if(rez.isEmpty())
			{
			return null;
			}
		return User.class.cast(rez.get(0));
		}
	
	/** answer a user by his login only  */
	protected User getUserByLogin(String login) throws ServletException
		{
		if(login==null) return null;
		List<?> rez= this.getTopLink().createNamedQuery("user.byLogin").
			setParameter("login", login).
			getResultList();
		if(rez.isEmpty())
			{
			return null;
			}
		return User.class.cast(rez.get(0));
		}
	
	/** get all PrjFin  available for this user */
	protected List<PrjFin> getPrjFinForUser(User user,OperonFactory f,PrjFin.Target target) throws DatabaseException
		{
		Set<Assembly> assemblies= new HashSet<Assembly>();
		for(Assembly as: Assembly.getAssemblies()) assemblies.add(as);
		return getPrjFinForUser(user,f,target,assemblies);
		}
	
	/** get all PrjFin  available for this user and those assembly */
	protected List<PrjFin> getPrjFinForUser(User user,OperonFactory f,PrjFin.Target target,Set<Assembly> assemblies) throws DatabaseException
		{
		List<PrjFin> array= new ArrayList<PrjFin>();
		PrjMgrDB.ValueIterator iter=null;
		AbstractIterator<PrjFin> iter2=null;
		try {
			iter=f.getPrjMgr().listValues();
			while(iter.hasNext())
				{
				PrjMgr p= iter.next();
	
				if(!user.canReadProject(p.getPid()))
					{
					//debug("user cannot read "+p);
					continue;
					}
				
				iter2= f.getPrjFin().listProjectsByIdAndTarget(p.getPid(),target);
				while(iter2.hasNext())
					{
					PrjFin prjfin = iter2.next();
					if(prjfin.getAssembly()==null) continue;
					if(!assemblies.contains(prjfin.getAssembly())) continue;
					array.add(prjfin);
					}
				iter2.close();
				iter2=null;
				
				}
			Collections.reverse(array);
			return array;
		} catch (Exception e) {
			debug(e);
			throw new DatabaseException(e);
		} finally
			{
			if(iter2!=null) iter.close();
			if(iter!=null) iter.close();
			}
		}
	
	
	
	
	protected void runSnpInfo(MultipartRequest multipartRequest,
			HttpServletResponse resp,
				OperonFactory factory,User user)
		throws ServletException,IOException,DatabaseException
		{
		if(multipartRequest==null || user==null || factory==null)
			{
			debug();
			throw new ServletException("Bad Request: runSnpIndo");
			}
		debug();
		List<PrjFin> prjfins=this.getPrjFinForUser(user, factory, PrjFin.Target.SNP);
		SNPInfo snpinfo=null;
		String area=null;
		String prjfin=null;
		File snpFile=null;
		File zipFile=null;
		debug();
		/* get panels */
		Set<MPanel> mpanels= new HashSet<MPanel>();
		if(multipartRequest.getParameterValues("mpanel")!=null)
			{
			for(String s:multipartRequest.getParameterValues("mpanel"))
				{
				if(!Cast.Integer.isA(s)) continue;
				MPanel mPanel= factory.getMPanel().get(Cast.Integer.cast(s));
				if(mPanel!=null) mpanels.add(mPanel);
				}
			}
		debug();
		/* get assembly */
		Set<Assembly> assemblies= new HashSet<Assembly>();
		if(multipartRequest.getParameterValues("as")!=null)
			{
			for(String s:multipartRequest.getParameterValues("as"))
				{
				if(!Cast.Integer.isA(s)) continue;
				Assembly build= Assembly.getAssemblyById(Cast.Integer.cast(s));
				if(build!=null) assemblies.add(build);
				}
			}
		debug();
		if(assemblies.isEmpty()) assemblies.add(Assembly.getDefaultAssembly());
		
		/* get prjdna for mouse genotypes */
		Set<String> prjdnasId= new HashSet<String>();
		if(multipartRequest.getParameterValues("prjfin4dna")!=null)
			{
			for(String s:multipartRequest.getParameterValues("prjfin4dna"))
				{
				if(factory.getPrjDna().containsKey(s))
					{
					prjdnasId.add(s);
					}
				}
			}
		debug();
		
		/* output */
		
		
		
		try {
			snpinfo= new SNPInfo();
			snpinfo.setFactory(factory);
			snpinfo.setMPanels(mpanels);
			snpinfo.setAssemblies(assemblies);
			snpinfo.setPrjdnaIds(prjdnasId);
			
			if(!isEmpty(prjfin=multipartRequest.getParameter("prjfin")))
				{
				//retrieve the prjfin in the list
				for(PrjFin p:prjfins)
					{
					if(!p.getProjCode().equals(prjfin)) continue;
					//save the snp in a temporary file
					PrintWriter out= null;
					DatabaseValueIterator<PrjSnp> iter=null;
					try {
						snpFile = File.createTempFile("prjfin", ".list",new File(getWebTmpDir()));
						out= new PrintWriter(snpFile);
						out.println("#SNP");
						
						iter=factory.getPrjSnp(p.getAssembly()).listValues(p.getProjCode());
						while(iter.hasNext())
							{
							out.println(iter.next().getAcn());
							}
						//set this as the source
						snpinfo.setInputSource(new InputSource.FILE(snpFile));
					} catch (Exception e) {
						throw new ServletException(e);
					}finally
						{
						if(iter!=null) iter.close();
						if(out!=null) { out.flush();out.close();}
						}
					break;
					}
				}
			else if(!isEmpty(area=multipartRequest.getParameter("area")))
				{
				area="#Name\n"+area;
				snpinfo.setInputSource(new InputSource.STRING(area));
				}
			else if((snpFile=multipartRequest.getFile("file"))!=null)
				{
				snpinfo.setInputSource(new InputSource.FILE(snpFile));
				}
			
			if(snpinfo.getInputSource()==null) throw new ServletException("no input");
			
			if(multipartRequest.getParameterValues("fmt")!=null)
				{
				for(String s:multipartRequest.getParameterValues("fmt"))
					{
					if(s.equals("snplex")) snpinfo.setSnplex(true);
					if(s.equals("sequenom")) snpinfo.setSequenom(true);
					if(s.equals("fasta")) snpinfo.setFasta(true);
					if(s.equals("taqman")) snpinfo.setTaqman(true);
					if(s.equals("illumina")) snpinfo.setIllumina(true);
					}
				}
			
			String delim= multipartRequest.getParameter("delim");
			if("tab".equals(delim)) snpinfo.setDelimiter(Pattern.compile("[\t]"));
			else if("comma".equals(delim)) snpinfo.setDelimiter(Pattern.compile("[,]"));
			
			String col=  multipartRequest.getParameter("column");
			if(Cast.Integer.isA(col))
				{
				int colIndex= Cast.Integer.cast(col);
				if(colIndex>0) snpinfo.setColIndex(colIndex-1);//convert to zero-based index
				}
			
			zipFile= snpinfo.makeZip(new File(getWebTmpDir()));
			
			resp.addHeader( "Content-Disposition", "attachment; filename="+ Ymd.now()+"_SNP.zip" ) ;
			resp.setContentType("application/zip");
			OutputStream stdout= resp.getOutputStream();
			InputStream in= new FileInputStream(zipFile);
			IOUtils.copyTo(in,stdout);
			stdout.flush();
			in.close();
			return;
			
			} 
		catch (Exception e) {
			debug(e);
			throw new ServletException(e);
			}
		finally
			{
			if(zipFile!=null) zipFile.delete();
			if(snpFile!=null) snpFile.delete();
			}

		
	}
	
	/** another version of snpInfo after I changed the JSP interface , changed parameters */
	protected void runSnpInfo(
				MultipartRequest multipartRequest,
				HttpServletResponse resp,
				OperonFactory factory,
				User user,
				Set<Assembly> assemblies,
				Set<MPanel> mpanels,
				Set<String> prjdnasId,
				Set<String> formats
				)
		throws ServletException,IOException,DatabaseException
			{
			SNPInfo snpinfo=null;
			verify(multipartRequest!=null);
			verify(resp!=null);
			verify(factory!=null);
			verify(user!=null);
			verify(assemblies!=null);
			verify(mpanels!=null);
			verify(prjdnasId!=null);
			verify(formats!=null);
			
			if(assemblies.isEmpty()) assemblies.add(Assembly.getDefaultAssembly());
			
			
			/* output */
	
				snpinfo= new SNPInfo();
				snpinfo.setFactory(factory);
				snpinfo.setMPanels(mpanels);
				snpinfo.setAssemblies(assemblies);
				snpinfo.setPrjdnaIds(prjdnasId);
				
				for(String s:formats)
					{
					if(s.equals("snplex")) snpinfo.setSnplex(true);
					if(s.equals("sequenom")) snpinfo.setSequenom(true);
					if(s.equals("fasta")) snpinfo.setFasta(true);
					if(s.equals("taqman")) snpinfo.setTaqman(true);
					if(s.equals("illumina")) snpinfo.setIllumina(true);
					}
				
			runSNPInfo(multipartRequest,resp,factory,snpinfo,user);	
			}
	
private void runSNPInfo(
		MultipartRequest multipartRequest,
		HttpServletResponse resp,
		OperonFactory factory,
		SNPInfo snpinfo,
		User user
		)
	throws ServletException,IOException
	{
	verify(multipartRequest!=null);
	verify(resp!=null);
	verify(factory!=null);
	verify(user!=null);
	verify(snpinfo!=null);
	
	
	File zipFile=null;
	File snpFile=null;
	try {
		String prjfin=null;
		String area=null;
		
		
		if(!isEmpty(prjfin=multipartRequest.getParameter("prjfin")))
			{
			List<PrjFin> prjfins=this.getPrjFinForUser(user, factory, PrjFin.Target.SNP);
			//retrieve the prjfin in the list
			for(PrjFin p:prjfins)
				{
				if(!p.getProjCode().equals(prjfin)) continue;
				//save the snp in a temporary file
				PrintWriter out= null;
				DatabaseValueIterator<PrjSnp> iter=null;
				try {
					snpFile = File.createTempFile("prjfin", ".list",new File(getWebTmpDir()));
					out= new PrintWriter(snpFile);
					out.println("#SNP");
					
					iter=factory.getPrjSnp(p.getAssembly()).listValues(p.getProjCode());
					while(iter.hasNext())
						{
						out.println(iter.next().getAcn());
						}
					//set this as the source
					snpinfo.setInputSource(new InputSource.FILE(snpFile));
				} catch (Exception e) {
					throw new ServletException(e);
				}finally
					{
					if(iter!=null) iter.close();
					if(out!=null) { out.flush();out.close();}
					}
				break;
				}
			}
		else if(!isEmpty(area=multipartRequest.getParameter("area")))
			{
			area="#Name\n"+area;
			snpinfo.setInputSource(new InputSource.STRING(area));
			}
		else if((snpFile=multipartRequest.getFile("file"))!=null)
			{
			snpinfo.setInputSource(new InputSource.FILE(snpFile));
			}
		
		if(snpinfo.getInputSource()==null) throw new ServletException("no input");
		
		
		
		snpinfo.setDelimiter(Pattern.compile("[\t]"));
		snpinfo.setColIndex(0);
		
		/*
		String delim= multipartRequest.getParameter("delim");
		if("tab".equals(delim)) snpinfo.setDelimiter(Pattern.compile("[\t]"));
		else if("comma".equals(delim)) snpinfo.setDelimiter(Pattern.compile("[,]"));
		
		String col=  multipartRequest.getParameter("column");
		if(Cast.Integer.isA(col))
			{
			int colIndex= Cast.Integer.cast(col);
			if(colIndex>0) snpinfo.setColIndex(colIndex-1);//convert to zero-based index
			}*/
		
		zipFile= snpinfo.makeZip(new File(getWebTmpDir()));
		
		resp.addHeader( "Content-Disposition", "attachment; filename="+ Ymd.now()+"_SNP.zip" ) ;
		resp.setContentType("application/zip");
		OutputStream stdout= resp.getOutputStream();
		InputStream in= new FileInputStream(zipFile);
		IOUtils.copyTo(in,stdout);
		stdout.flush();
		in.close();
		return;
		
		} 
	catch (Exception e) {
		debug(e);
		throw new ServletException(e);
		}
	finally
		{
		if(zipFile!=null) zipFile.delete();
		if(snpFile!=null) snpFile.delete();
		}
	}
	
	
}
